package model.minimax;

import java.util.ArrayList;


import model.GamePiece;
import model.LadderGame;
import model.Slot;

/**
 * This class consists of the heuristic implementation for the game.
 * It evaluates the appropriate value of each grid configuration and returns to the minimax algo.
 * It also has ladderPattterns arraylist that stores all possible ladder formations. 
 * This class also checks for ladder neutralization situations.
 *
 */
public class Evaluator
{
    /**
     * Max heuristic value a game state can have.
     */
    public final static int INFINITY = 1000000;

    /**
     * A Collection of all Possible Ladder Patterns to make in the game.
     */
    private static ArrayList<LadderPattern> ladderPatterns;

    static
    {
        // Initializing ladderPatterns
        Evaluator.ladderPatterns = new ArrayList<LadderPattern>();

        for (int i = 0; i < LadderGame.GRID_ROW_SIZE; i++)
        {
            int columnSize = 2 * (LadderGame.GRID_ROW_SIZE - i) - 1;
            for (int j = 0; j < columnSize; j++)
            {
                Slot slot = Slot.get(i, j);

                try
                {
                    // adding left ladder pattern
                    Slot[] rightAdjacentSlots = slot.getRightAndUpperAdjacentSlots();
                    Slot upperleftSlot = slot.getUpperLeftSlot();
                    Slot topLadderSlot = upperleftSlot.getUpperSlot();

                    if (rightAdjacentSlots[0] != null && rightAdjacentSlots[1] != null && upperleftSlot != null && topLadderSlot != null)
                    {
                        Evaluator.ladderPatterns.add(new LadderPattern(true, rightAdjacentSlots[0], slot, rightAdjacentSlots[1], upperleftSlot, topLadderSlot));
                    }
                }
                catch (NullPointerException e)
                {
                    // nothing to do
                }

                try
                {
                    // adding right ladder pattern
                    Slot[] leftAdjacentSlots = slot.getLeftAndUpperAdjacentSlots();
                    Slot upperRightSlot = slot.getUpperRightSlot();
                    Slot topLadderSlot2 = upperRightSlot.getUpperSlot();

                    if (leftAdjacentSlots[0] != null && leftAdjacentSlots[1] != null && upperRightSlot != null && topLadderSlot2 != null)
                    {
                        Evaluator.ladderPatterns
                                .add(new LadderPattern(false, leftAdjacentSlots[0], slot, leftAdjacentSlots[1], upperRightSlot, topLadderSlot2));
                    }
                }
                catch (NullPointerException e)
                {
                    // do nothing
                }
            }
        }
    }

    /**
     * returns the heuristic evaluation value of the passed grid state.
     * 
     * @param grid
     * @return
     */
    public static int evaluate(ArrayList<ArrayList<GamePiece>> grid)
    {
        int totalScore = 0;

        for (LadderPattern ladderPattern : Evaluator.ladderPatterns)
        {
            int patternScore = getPatternScore(ladderPattern, grid);
            if (patternScore == INFINITY)
            {
                totalScore = INFINITY;
                break;
            } 
            else if (patternScore == -INFINITY)
            {
                totalScore = -INFINITY;
                break;                
            }
            else
            {
                totalScore += patternScore;   
            }
        }

        return totalScore;
    }

    /**
     * Returns a heuristic evaluation of one specific ladderPattern
     * 
     * @param ladderPattern
     *            the ladderPattern to evaluate
     * @param grid
     *            the grid to check ladderPattern score against
     * @return the following score based on the passed grid :
     * 
     *   0 if the ladderPattern contains at least one piece from both players
     *   OR if the ladderPattern does not contain any pieces 
     *   OR if the ladderPattern contains at least one piece from one player but is already neutralized by the other player
     *   
     *   1 * (PlayerFactor) if ladderPattern contains one piece.
     *   
     *   5 * (PlayerFactor) if ladderPattern contains 2 pieces.
     *   
     *   100 * (PlayerFactor) if ladderPattern contains 3 pieces.
     *   
     *   1000 * (PlayerFactor) if ladderPattern contains 4 pieces.
     *   
     *   INFINITY * (PlayerFactor) if ladderPattern contains 5 pieces.
     *   
     *   Note : PlayerFactor = 
     *   1 if the ladderPattern contain only WHITE pieces.
     *   -1 if the ladderPattern contain only BLACK pieces.
     */
    private static int getPatternScore(LadderPattern ladderPattern, ArrayList<ArrayList<GamePiece>> grid)
    {
        int numOfWhites = 0;
        int numOfBlacks = 0;

        for (Slot slot : ladderPattern.getSlots())
        {
            switch (grid.get(slot.getRowIndex()).get(slot.getColumnIndex()))
            {
                case BLACK:
                    numOfBlacks++;
                    break;
                case WHITE:
                    numOfWhites++;
                    break;
                default:
                    break;
            }
        }

        if (numOfWhites == 0)
        {
            if (ladderPatternIsNeutralizedByPiece(ladderPattern, GamePiece.WHITE, grid))
            {
                return 0;
            }
            else
            {
                switch (numOfBlacks)
                {
                    case 0:
                        return 0;
                    case 1:
                        return -1;
                    case 2:
                        return -5;
                    case 3:
                        return -100;
                    case 4:
                        return -1000;
                    case 5:
                        return -INFINITY;
                    default:
                        return -INFINITY;
                }
            }
        }
        else if (numOfBlacks == 0)
        {
            // return zero if ladderPattern is neutralized by black
            if (ladderPatternIsNeutralizedByPiece(ladderPattern, GamePiece.BLACK, grid))
            {
                return 0;
            }
            else
            {
                switch (numOfWhites)
                {
                    case 0:
                        return 0;
                    case 1:
                        return 1;
                    case 2:
                        return 5;
                    case 3:
                        return 100;
                    case 4:
                        return 1000;
                    case 5:
                        return INFINITY;
                    default:
                        return INFINITY;
                }
            }
        }
        else
        {
            return 0;
        }
    }

    /**
     * Returns true if the passed ladderPattern is neutralized by the passed
     * Gamepiece
     * 
     * @param ladderPattern
     * @param gamePiece
     * @param grid
     * @return
     */
    private static boolean ladderPatternIsNeutralizedByPiece(LadderPattern ladderPattern, GamePiece gamePiece, ArrayList<ArrayList<GamePiece>> grid)
    {
        try
        {
            Slot[] neutralizingSlots = ladderPattern.getNeutralizingSlots();
            return grid.get(neutralizingSlots[0].getRowIndex()).get(neutralizingSlots[0].getColumnIndex()) == gamePiece
                    && grid.get(neutralizingSlots[1].getRowIndex()).get(neutralizingSlots[1].getColumnIndex()) == gamePiece;
        }
        catch (NullPointerException e)
        {
            return false;
        }

    }
}
